/**
 * Copyright (C) 2007 Logan Johnson
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jacky.lanlan.song.junitx.v4.atunit;

import jacky.lanlan.song.junitx.v4.atunit.core.*;
import jacky.lanlan.song.junitx.v4.atunit.easymock.EasyMockFramework;
import jacky.lanlan.song.junitx.v4.atunit.guice.GuiceContainer;
import jacky.lanlan.song.junitx.v4.atunit.mockito.MockitoFramework;
import jacky.lanlan.song.junitx.v4.atunit.spring.SpringContainer;
import jacky.lanlan.song.junitx.v4.conc.ConcurrentRunner;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.junit.internal.runners.InitializationError;


/**
 * This is the JUnit test runner used for AtUnit tests. It delegates to the
 * configured {@link jacky.lanlan.song.junitx.v4.atunit.core.MockGenerator} for mock and stub object
 * creation and to the configured {@link jacky.lanlan.song.junitx.v4.atunit.core.Container} for dependency
 * injection.
 * <p>
 * And finally, this {@link Runner} is a subclass of {@link ConcurrentRunner}, which means it can run the
 * test concurrently, see the {@link ConcurrentRunner} for details.
 * 
 * @author Logan Johnson &lt;logan.johnson@gmail.com&gt;
 * @author Jacky.Song
 */
public class AtUnit extends ConcurrentRunner {

	public AtUnit(Class<?> testClass) throws InitializationError {
		super(testClass);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	protected Object createTest() throws Exception {
		Class<?> c = getTestClass().getJavaClass();
		Set<Field> testFields = getFields(c);
		
		Container container = getContainerFor(c);
		MockGenerator mockGenerator = getMockGeneratorFor(c);
				
		// make sure we have one (and only one) @Unit field
		Field unitField = getUnitField(testFields);
		if ( unitField.getAnnotation(Mock.class) != null ) {
			throw new IncompatibleAnnotationException(Unit.class, Mock.class);
		}
		
		//得到 [Field -> Mock对象] 表
		final Map<Field,Object> fieldValues = mockGenerator.generateMocks(testFields.toArray(new Field[0]));
		if ( fieldValues.containsKey(unitField)) {
			throw new IncompatibleAnnotationException(Unit.class, unitField.getType());
		}
		
		//再把测试类和fieldValues传给IoC容器，让IoC容器按照容器自己的方式对测试类实施DI，fieldValues一般不用，不过也传进去以防万一
		Object test = container.createTest(c, fieldValues);

		// 最后处理那些还没有任何值的(null)Field，一般情况下都是那些需要Mock(标注了@Mock)的Field
		// any field values created by AtUnit but not injected by the container are injected here.
		for ( Field field : fieldValues.keySet() ) {
			field.setAccessible(true);
			if ( field.get(test) == null ) {
				field.set(test, fieldValues.get(field));
			}
		}
		
		return test;// 最后，再把准备好的实例对象交回Junit，执行测试
	}

	
	/**
	 * Gets all declared fields and all inherited fields.
	 */
	protected Set<Field> getFields(Class<?> c) {
		Set<Field> fields = new HashSet<Field>(Arrays.asList(c.getDeclaredFields()));
		while ( (c = c.getSuperclass()) != null ) {
			for ( Field f : c.getDeclaredFields() ) {
				if ( !Modifier.isStatic(f.getModifiers())
					 && !Modifier.isPrivate(f.getModifiers())
				     ) {
					fields.add(f);
				}
			}
		}
		return fields;
	}
	
	protected Field getUnitField(Set<Field> fields) throws NoUnitException, TooManyUnitsException {
		Field unitField = null;
		for ( Field field : fields ) {
			for ( Annotation anno : field.getAnnotations() ) {
				if ( Unit.class.isAssignableFrom(anno.annotationType())) {
					if ( unitField != null ) throw new TooManyUnitsException("Already had field " + unitField + " when I found field " + field);
					unitField = field;
				}
			}
		}
		if ( unitField == null ) throw new NoUnitException();
		return unitField;
	}
	
	
	protected Container getContainerFor(Class<?> testClass) throws Exception {
		Class<? extends Container> containerClass = null;
		
		IoC ioc = testClass.getAnnotation(IoC.class);
		ContainerClass containerClassAnno = testClass.getAnnotation(ContainerClass.class);
		
		if ( ioc != null && containerClassAnno != null )
			throw new IncompatibleAnnotationException(IoC.class, ContainerClass.class);

		if ( ioc != null ) {
			switch ( ioc.value() ) {
				case GUICE: containerClass = GuiceContainer.class; break;
				case SPRING: containerClass = SpringContainer.class; break;
			}
		}
		else {
			containerClass = GuiceContainer.class;
		}
		
		if ( containerClassAnno != null ) {
			containerClass = containerClassAnno.value();
		}
		
		return containerClass.newInstance();
	}
	
	protected MockGenerator getMockGeneratorFor(Class<?> testClass) throws Exception {
		Class<? extends MockGenerator> mockGeneratorClass = null;
		
		InjectMock injectMockAnno = testClass.getAnnotation(InjectMock.class);
		MockFrameworkClass mockFrameworkClassAnno = testClass.getAnnotation(MockFrameworkClass.class);
		
		if ( injectMockAnno != null && mockFrameworkClassAnno != null )
			throw new IncompatibleAnnotationException(InjectMock.class, MockFrameworkClass.class);

		if ( injectMockAnno != null ) {
			switch ( injectMockAnno.value() ) {
				case EASYMOCK: mockGeneratorClass = EasyMockFramework.class; break;
				case MOCKITO: mockGeneratorClass = MockitoFramework.class; break;
			}
		}
		else {
			mockGeneratorClass = MockitoFramework.class;
		}
		
		if ( mockFrameworkClassAnno != null ) {
			mockGeneratorClass = mockFrameworkClassAnno.value();
		}
		
		return mockGeneratorClass.newInstance();
	}

}
